package controller;

import java.io.File;
import java.util.List;

import model.Project;
import model.Test;
import modelInterfaces.AbstractModelFactory;
import modelInterfaces.IProject;
import modelInterfaces.ITest;
import pluginFactory.CompositionPluginFactory;
import pluginFactory.MetricPluginFactory;
import pluginFactory.RecommenderPluginFactory;
import pluginFactory.SimilarityMeasurePluginFactory;
import pluginInterfaces.IMetric;
import pluginInterfaces.IRecommender;
import utils.Codes;
import utils.Messages;
import utils.ReadProperties;
import view.ViewMain;
import viewInterfaces.AbstractViewFactory;
import viewInterfaces.IViewMain;
import controllerInterfaces.AbstractControllerFactory;
import controllerInterfaces.IControllerBenchmarkPanel;
import controllerInterfaces.IControllerCLI;
import controllerInterfaces.IControllerDBConnectionSetup;
import controllerInterfaces.IControllerMain;
import controllerInterfaces.IControllerMetricsPanel;
import controllerInterfaces.IControllerProjectManagement;
import controllerInterfaces.IControllerRecommendersPanel;
import controllerInterfaces.IControllerRunSetup;
import controllerInterfaces.IControllerTestsPanel;
import enums.StatusProject;
import enums.Use;


/**
 * 
 * MVC++ Main Controller Class
 *
 */
public class ControllerMain implements IControllerMain {

	/**  Controller performs I/O actions ({@link Use}) on a {@link Project} */
	private IControllerProjectManagement controllerProjectManagement;
	
	/**  Controller manages each of recommendation algorithm configurations*/
	private IControllerRecommendersPanel controllerRecommendersPanel;
    
	/** Controller manages each of metric configurations*/
	private IControllerMetricsPanel controllerMetricsPanel;
	
	/** Controller manages each of {@link Test}*/
    private IControllerTestsPanel controllerTestsPanel;
    
    /** Controller allow to perform different {@link Test} */
    private IControllerBenchmarkPanel controllerBecnhmarkPanel;
    
    /** Controller manages the database connection. */
    private IControllerDBConnectionSetup controllerDBConnectionSetup;
    
    /** Controller manages the execution of a {@link Recommender}.*/
    private IControllerRunSetup controllerRun;
    
    /** Controller manages the Command Line Mode execution*/
    private IControllerCLI controllerCLI;
    
    /** MVC++ View Main*/
    private IViewMain view;
    
    /**Rbox Version**/
    private String rboxVersion = "Rbox 1.2";
    
    /** Interface of current project*/
    private IProject project;
    
    /** indicate if a {@link Project} is enabled.*/
    private boolean projectEnabled; 
    
    /** indicate if database is connected.*/
    private boolean databaseConnected;     

    /** selected file associated with {@link Project} */
    private File sFile;
    
    /** indicate if application is running on debug mode **/
    private boolean isDebug = false;
    
    /** indicate Rbox execution mode. Default, graphic user interface (GUI)**/
    private String EJECUTION_MODE = "GUI";
    
    /**
     * Class constructor
     */
    public ControllerMain()
    {
    	InitGUI();
    }


    
    /**
     * Class contructor for RBox on Commands Line RBox
     * @param args loaded arguments list from command line
     */
    public ControllerMain(String[] args)
    {
    	for(String key :args)
    	{
    		if(key.toUpperCase().equals("--MODE=GUI"))
    		{
    			EJECUTION_MODE = "GUI";
    			break;
    		}
    		if(key.toUpperCase().equals("--MODE=CLI"))
    		{
    			EJECUTION_MODE = "CLI";
    			break;
    		}
    	}
    	
    	/*Modo de ejecutar la aplicación*/
    	if(EJECUTION_MODE == "GUI")
    	{
    		InitGUI();
    	}
    	else
    	{
    		InitCLI(args);
    	}
    	
    }
    
    
    /**
     * Starts RBox on Commands Line mode
     * @param args loaded arguments list from command line
     */
    private void InitCLI(String[] args)
    {
    	/*Initialize plugins*/
		 RecommenderPluginFactory.retrieveAll();
		 MetricPluginFactory.retrieveAll();
		 SimilarityMeasurePluginFactory.retrieveAll();
		 CompositionPluginFactory.retrieveAll();
    	
    	this.controllerCLI = AbstractControllerFactory.getAbstractControllerFactory().getControllerCLI();
    	this.controllerCLI.setMainController(this);
    	this.controllerCLI.loadArguments(args);
    	this.controllerCLI.execute();
    }
    
    
    /**
     * Starts RBox on Graphic User Interface mode
     */
    private void InitGUI()
    {
    	/*Generating  panels*/
    	this.controllerRecommendersPanel = AbstractControllerFactory.getAbstractControllerFactory().getControllerRecommendersPanel();
    	this.controllerRecommendersPanel.SetMainController(this);
    	this.controllerRecommendersPanel.blockAllComponents();
    	
    	this.controllerMetricsPanel = AbstractControllerFactory.getAbstractControllerFactory().getControllerMetricsPanel();
    	this.controllerMetricsPanel.SetMainController(this);
    	this.controllerMetricsPanel.blockAllComponents();
    	
    	this.controllerTestsPanel = AbstractControllerFactory.getAbstractControllerFactory().getControllerTestPanel();
    	this.controllerTestsPanel.SetMainController(this);
    	this.controllerTestsPanel.blockAllComponents();

    	this.controllerBecnhmarkPanel = AbstractControllerFactory.getAbstractControllerFactory().getControllerBenchmarkPanel();
    	this.controllerBecnhmarkPanel.setMainController(this);
    	//TODO [Andy] this.controllerBecnhmarkPanel.blockAllComponents();
    	
    	
    	/*Generating Main View*/
    	this.view = AbstractViewFactory.getAbstractViewFactory().getViewMain();
    	this.view.SetTabbedPane("Recommenders", this.controllerRecommendersPanel.getPanel());
    	this.view.SetTabbedPane("Metrics", this.controllerMetricsPanel.getPanel());
    	this.view.SetTabbedPane("Test", this.controllerTestsPanel.getPanel());
    	this.view.SetTabbedPane("Benchmark", this.controllerBecnhmarkPanel.getPanel());
    	
    	/* Initialize buttons of view main*/
    	this.view.setNewProjectButtonEnabled(true);
    	this.view.setLoadProjectButtonEnabled(true);
    	this.view.setSaveProjectButtonEnabled(false);
    	this.view.setSaveAsProjectButtonEnabled(false);
    	
    	
    	/*Initialize plugins*/
		 RecommenderPluginFactory.retrieveAll();
		 MetricPluginFactory.retrieveAll();
		 SimilarityMeasurePluginFactory.retrieveAll();
		 CompositionPluginFactory.retrieveAll();
    	
		/*Initialize project execution variables*/
		 this.projectEnabled = false;
		 this.databaseConnected = false;
		 try
		 {
			ReadProperties.readDBAccesData("config/database.props");
		 }
		 catch (Exception e)
		 {
			view.renderError(Codes.errorLoadDBProperties, Messages.errorLoadDBProperties);
			e.printStackTrace();
		 }
		 
    	this.view.SetController(this);
        this.view.render();
    }
    
    
    
    /**
     * Closes the Main View 
     */
    public void close()
    {
    	this.view.close();
    }
    
    /**
     * Terminates normally the application
     */
    public void normalExit()
    {
    	System.exit(0);
    }
    
    /**
     * Terminates abnormally the application
     */
    public void abnormalExit()
    {
    	System.exit(-1);
    }


	/**
	 * Get the list of current {@link Project} {@link Test}.</br>
	 * Each of the obtained {@link Test} is manipulable through its {@link ITest} interface.
	 * @return	list of current project tests
	 * @see 	ITest
	 * @see		Test
	 */
	public List<ITest> getTestList() {
		if(this.project == null)
		{
			return null;
		}
		
		return this.project.getTestList();		
	}

    
	/**
	 * Saves the list of current {@link Project} {@link Test}.</br>
	 * Each of the saved {@link Test} is manipulable through its {@link ITest} interface.
	 * @param	testList	list of {@link ITest}
	 * @return	list of current {@link Project} {@link Test}.
	 * @see 	ITest
	 * @see		Project
	 * @see		Test
	 */
	public void setTestList(List<ITest> testList) {
		this.project.setTestList(testList);	
	}


	/**
	 * Get the list of current {@link Project} {@link Recommender}.</br>
	 * Each of the obtained {@link Recommender} is manipulable through its {@link IRecommender} interface.
	 * @return	list of current {@link Project} {@link Recommender}.
	 * @see 	IRecommender
	 * @see		Project
	 * @see		Recommender
	 */	
	public List<IRecommender> getRecommenderList() {
		if(this.project == null)
		{
			return null;
		}
		
		return this.project.getRecommenderList();
	}

	
	/**
	 * Saves the list of current {@link Project} {@link Recommender}.</br>
	 * Each of the saved {@link Recommender} is manipulable through its {@link IRecommender} interface.
	 * @param	reocommenderList	list of {@link IRecommender}
	 * @see 	IRecommender
	 * @see		Project
	 * @see		Recommender
	 */	
	public void setRecommenderList(List<IRecommender> recommenderList) {
		this.controllerRecommendersPanel.setRecommenderList(recommenderList);
	}	
	
	/**
	 * Get the list of current {@link Project} {@link Metric}.</br>
	 * Each of the obtained {@link Metric} is manipulable through its {@link IMetric} interface.
	 * @return	list of current {@link Project} {@link Metric}.
	 * @see 	IMetric
	 * @see		Project
	 * @see		Metric
	 */			
	public List<IMetric> getMetricList() {
		if(this.project == null)
		{
			return null;
		}
				
		return this.project.getMetricList();
	}

	
	/**
	 * Instance the controller manages the execution of a {@link Recommender}.
	 * @param	recommender	recommendation algorithm to execute
	 * @see		Recommender
	 */
	public void startRunSetup(IRecommender recommender) {
		this.controllerRun = AbstractControllerFactory.getAbstractControllerFactory().getControllerRun();
		this.controllerRun.SetMainController(this);
		this.controllerRun.SetRecommender(recommender);
	}

	
	/**
	 * Finish the controller manages the execution of a {@link Recommender}.
	 * @see		Recommender 
	 */
	public void stopRun() {
		this.controllerRun = null;
	}


	
	/**
	 * Instance the controller manages the database connection.
	 */
	public void startDBConnectionSetup() {
		this.controllerDBConnectionSetup = AbstractControllerFactory.getAbstractControllerFactory().getControllerDBConnectionSetup();
		this.controllerDBConnectionSetup.SetMainController(this);
		this.controllerDBConnectionSetup.showView();
		
	}


	/**
	 * Finish the controller manages the database connection.
	 */
	public void stopDBConnectionSetup() {
		this.controllerDBConnectionSetup = null;
	}



	/**
	 * Instance the controller performs I/O actions ({@link Use}) on a {@link Project}
	 * @param	use		the action to will perform on a {@link Project}
	 * @see 	Use
	 * @see 	Project
	 */	
	public void startProjectManagement(Use use) {
		this.controllerProjectManagement =  AbstractControllerFactory.getAbstractControllerFactory().getControllerProjectManagement();
		this.controllerProjectManagement.setMainController(this);
		this.controllerProjectManagement.SetUse(use);
		this.controllerProjectManagement.load();
	}
	
	
	/**
	 * Finish the controller performs I/O actions ({@link Use}) on a {@link Project}
	 * @see 	Use
	 * @see 	Project
	 */	
	public void stopProjectManagement() {
		this.controllerProjectManagement = null;
		
	}


	/**
	 * Get the {@link ViewMain}.
	 * {@link ViewMain} is manipulable through {@link IViewMain} interface.
	 * @return 	IViewMain	interface of {@link ViewMain]
	 * @see ViewMain
	 * @see IViewMain	
	 */
	public IViewMain getViewMain() {
		return this.view;
	}

	
	
	/**
	 * Get the current {@link Project}.</br>
	 * The {@link Project} is manipulable through {@link IProject} interface
	 * @return IProject	interface of {@link Project}
	 * @see Project
	 * @see IProject
	 */
	public IProject getProject(){
		if (this.project == null)
		{
			return null;
		}
		return this.project;
	}
	

	/**
	 * Save the current {@link Project}
	 * The {@link Project} is manipulable through {@link IProject} interface
	 * @param	project	The project to save
	 * @see 	Project
	 * @see 	IProject
	 */	
	public void setProject(IProject project) {
		if(this.project == null)
		{
			this.project = AbstractModelFactory.getModelFactory().getProject();
		}
		
		this.project = project;
		this.refreshPanels();
		this.StatusProjectModified();
	}
	
	/**
	 * Refresh each of panels 
	 */
	private void refreshPanels() {
		this.controllerRecommendersPanel.setRecommenderList(this.project.getRecommenderList());
		this.controllerMetricsPanel.setMetricList(this.project.getMetricList());
		this.controllerTestsPanel.loadTestsToView();
		this.controllerBecnhmarkPanel.loadTestsToView();
		this.UnBlockPanels();
	}


	/**
	 * Set the {@link Project} name in window title
	 * @param 	name	{@link Project} name
	 * @see		ViewMain
	 */
	public void setProjectNameInView(String name)
	{
		this.view.setProjectNameinFrame(name);
	}


	
	public boolean removeAlgorithmIntoTest(int index){
		IRecommender alg = getRecommenderList().get(index);
		for(ITest test : this.controllerTestsPanel.getTests()){
			if(test.getAlgorithms().contains(alg)){
				test.getAlgorithms().remove(alg);
				return true;
			}
		}
		return false;
	}



	public void removeMetricIntoTest(int index) {
		IMetric metric = getMetricList().get(index);
		for(ITest test : this.controllerTestsPanel.getTests()){
			if(test.getMetrics().contains(metric)){
				test.getMetrics().remove(metric);
				return;
			}
		}		
	}


	/**
	 * Block each of panels of {@link ViewMain}.
	 * @see ViewMain
	 */	
	public void BlockPanels() {
		controllerRecommendersPanel.blockAllComponents();
	    controllerMetricsPanel.blockAllComponents();
	    controllerTestsPanel.blockAllComponents();
	    //TODO [Andy] controllerBecnhmarkPanel.blockAllComponents();

	}

	
	/**
	 * Unblock each of panels of {@link ViewMain}.
	 * @see ViewMain
	 */	
	public void UnBlockPanels() {
		controllerRecommendersPanel.unBlockAllComponents();
	    controllerMetricsPanel.unBlockAllComponents();
	    controllerTestsPanel.unBlockAllComponents();
	    //TODO [Andy] controllerBecnhmarkPanel.unBlockAllComponents();
		
	}
	

	/**
	 * Indicate if a {@link Project} is open.
	 * @return true or false as required
	 * @see Project
	 */
	public boolean GetProjectEnabled()
	{
		return this.projectEnabled;
	}

	
	
	/**
	 * Set if a {@link Project} is enabled.
	 * @return true or false as required.
	 * @see Project
	 */
	public void SetProjectEnabled(boolean value){
		this.projectEnabled = value;
	}


	
	/**
	 * Indicate if database is connected.
	 * @return true or false as required.
	 */
	public boolean GetDatabaseConnected() {
		return this.databaseConnected;
	}

	

	/**
	 * Set if database is connected.
	 * @return true or false as required.
	 */
	public void SetDatabaseConnected(boolean value) {
		this.databaseConnected = value;
	}

	
	
	/**
	 * Set the status of {@link Project} as created and realize operations</br>
	 * for refresh view and controller status.
	 */ 
	public void StatusProjectCreated(){
		this.project.setStatus(StatusProject.New);
		this.view.setProjectNameinFrame(this.project.getName());
		this.view.setSaveAsProjectButtonEnabled(true);
		this.view.setSaveProjectButtonEnabled(true);
		this.view.SetLabelProjectStatus("Created", true);	
		this.view.SetLabelFileName("None", false);
		this.SetSelectedFile(null);
		this.projectEnabled = true;
		this.UnBlockPanels();
		this.stopProjectManagement();
	}
	
	
	
	/**
	 * Set the status of {@link Project} as modified and realize operations</br>
	 * for refresh view and controller status.
	 */    
	public void StatusProjectModified() {
		this.project.setStatus(StatusProject.Modified);
		this.view.setSaveAsProjectButtonEnabled(true);
		this.view.setSaveProjectButtonEnabled(true);
		this.view.SetLabelProjectStatus("Modified", true);
		if(this.sFile != null)
		{
			this.view.SetLabelFileName(this.sFile.getAbsolutePath(), true);
		}
	}

	
	
	/**
	 * Set the status of {@link Project} as saved and realize operations</br>
	 * for refresh view and controller status.
	 */    
	public void StatusProjectSaved() {
		this.project.setStatus(StatusProject.Saved);
		this.view.setSaveAsProjectButtonEnabled(true);
		this.view.setSaveProjectButtonEnabled(true);
		this.view.SetLabelProjectStatus("Saved", true);
		this.view.SetLabelFileName(this.sFile.getAbsolutePath(), true);
		this.stopProjectManagement();
	}


	
	/**
	 * Indicate if {@link Project} status is modified.
	 * @return true when project status is modified, otherwise false.
	 * @see Project 
	 */
	public boolean isModifiedProject() {
		if (this.project != null && this.project.getStatus() == StatusProject.Modified)
		{
			return true;
		}
		else
		{
			return false;
		}
	}	

	
	
	/**
	 * Set selected file associated with {@link Project}. 
	 * @param oi object input stream.
	 * @see Project
	 */
	public void SetSelectedFile(File sFile)
	{
		this.sFile = sFile;
	}
	

	
	/**
	 * Get selected file associated with {@link Project}. 
	 * @return file associated with project.
	 * @see Project
	 */
	public File GetSelectedFile()
	{
		return this.sFile;
	}


	
	/**
	 * Set status of database connection according to parameter value.
	 * @param value	if true then database connection status is connected, otherwise, disconnected .
	 */
	public void setDBConnectedStatus(boolean value) {
		
		String str = "Disconnected";
		 
		if(value)
		{
			str = "Connected"; 
		}
		
		this.view.setLabelDBStatus(str, value);
		this.databaseConnected = value;
	}


	
	/**
	 * Set status of project according to parameter value.
	 * @param value	if true then project status is enabled, otherwise, disabled .
	 */
	public void setProjectStatus(boolean value)
	{
		this.projectEnabled = value;
	}


	/**
	 * Set status of project in controller, model and view levels according to parameter value and status.
	 * @param value	if true then project status is enabled, otherwise, disabled.
	 * @param status status of project according to {@link StatusProject}.
	 */
	public void setProjectStatus(boolean value, StatusProject status)
	{
		this.projectEnabled = value;
		this.project.setStatus(status);
		this.view.SetLabelProjectStatus(status.toString(), value);
	}	
	
	
	
	/**
	 * Get status of DB connection.
	 * @return if true then database connection exists, otherwise, doesn't exist .
	 */
	public boolean getDBConnectedStatus() {
		return this.databaseConnected;
	}


	
	/**
	 * Get status of project.
	 * @return if true then project status is enabled, otherwise, disabled .
	 */
	public boolean getProjectStatus() {
		return this.projectEnabled;
	}


	
	/**
	 * Get ControllerProjectManagement Interface.</br>
	 * This method is only used for realize unit testing.
	 * @return IControllerProjectManagement
	 */
	public IControllerProjectManagement getControllerProjectManagement() {
		return controllerProjectManagement;
	}


	
	/**
	 * Get ControllerRecommendersPanel Interface.</br>
	 * This method is only used for realize unit testing.
	 * @return IControllerRecommendersPanel
	 */	
	public IControllerRecommendersPanel getControllerRecommendersPanel() {
		return controllerRecommendersPanel;
	}


	
	/**
	 * Get ControllerMetricsPanel Interface.</br>
	 * This method is only used for realize unit testing.
	 * @return IControllerMetricsPanel
	 */		
	public IControllerMetricsPanel getControllerMetricsPanel() {
		return controllerMetricsPanel;
	}


	
	/**
	 * Get ControllerTestsPanel Interface.</br>
	 * This method is only used for realize unit testing.
	 * @return IControllerProjectManagement
	 */	
	public IControllerTestsPanel getControllerTestPanel() {
		return controllerTestsPanel;
	}

	

	/**
	 * Get ControllerBenchmarkPanel Interface.</br>
	 * This method is only used for realize unit testing.
	 * @return IControllerBenchmarkPanel
	 */	
	public IControllerBenchmarkPanel getControllerBecnhmarkPanel() {
		return controllerBecnhmarkPanel;
	}


	
	/**
	 * Get ControllerDBConnectionSetup Interface.</br>
	 * This method is only used for realize unit testing.
	 * @return IControllerDBConnectionSetup
	 */		
	public IControllerDBConnectionSetup getControllerDBConnectionSetup() {
		return controllerDBConnectionSetup;
	}


	
	/**
	 * Get ControllerRunSetup Interface.</br>
	 * This method is only used for realize unit testing.
	 * @return IControllerRunSetup
	 */		
	public IControllerRunSetup getControllerRun() {
		return controllerRun;
	}

	
	/**
	 * Indicate if application is running on debug mode.
	 * @return value of debug mode.
	 */
	public boolean isDebug(){
		return this.isDebug;
	}
	
	/**
	 * Indicates RBox Version
	 * @return RBox version
	 */
	public String getRboxVersion()
	{
		return this.rboxVersion;
	}

}

